﻿using LightCAD.MathLib;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Three
{
    public class FontLoader : Loader<Font>
    {
        public FontLoader(LoadingManager manager = null) : base(manager)
        {
        }

        public override Font load(string url, Action<Font> onLoad, onProcessDelegate onProgress = null, onErrorDelegate onError = null)
        {
            var scope = this;
            var loader = new FileLoader(this.manager);
            loader.setPath(this.path);
            loader.setRequestHeader(this.requestHeader);
            loader.setResponseType("json");
            loader.setWithCredentials(this.withCredentials);
            Font font = null;
            loader.load(url, (text) =>
            {
                //font = scope.parse(text as JObject);
                if (onLoad != null)
                    onLoad(font);
            }, onProgress, onError);
            return font;
        }
        public override async Task loadAsync(string url, Action<Font> onLoad, onProcessDelegate onProgress = null, onErrorDelegate onError = null)
        {
            var scope = this;
            var loader = new FileLoader(this.manager);
            loader.setPath(this.path);
            loader.setRequestHeader(this.requestHeader);
            loader.setResponseType("json");
            loader.setWithCredentials(this.withCredentials);
            Font font = null;
            await loader.loadAsync(url, (text) =>
            {
                //font = scope.parse(text as JObject);
                if (onLoad != null)
                    onLoad(font);
            }, onProgress, onError);
        }
        //public Font parse(JObject json)
        //{
        //    return new Font(new FontData().SetFromJson(json));
        //}
    }

    public class FontGlyph
    {
        public string o;
        public string[] _cachedOutline;
        public double x_min;
        public double x_max;
        public double ha;
    }

    public class FontBoundingBox
    {
        public double yMin;
        public double yMax;
        public double xMin;
        public double xMax;
    }
    public class FontData
    {
        public JsObj<FontGlyph> glyphs;
        public double resolution;
        public double underlinePosition;
        public double underlineThickness;
        public FontBoundingBox boundingBox;
        public string familyName;
        public double ascender;
        public double descender;

        //public FontData SetFromJson(JObject jobj)
        //{
        //    this.ascender = (double)jobj.GetValue("ascender");
        //    this.descender = (double)jobj.GetValue("descender");
        //    this.resolution = (double)jobj.GetValue("resolution");
        //    this.underlinePosition = (double)jobj.GetValue("underlinePosition");
        //    this.underlineThickness = (double)jobj.GetValue("underlineThickness");
        //    this.familyName = (string)jobj.GetValue("familyName");

        //    this.boundingBox = new FontBoundingBox();
        //    var bbobj = (JObject)jobj.GetValue("boundingBox");
        //    this.boundingBox.xMin = (double)bbobj.GetValue("xMin");
        //    this.boundingBox.xMax = (double)bbobj.GetValue("xMax");
        //    this.boundingBox.yMin = (double)bbobj.GetValue("yMin");
        //    this.boundingBox.yMax = (double)bbobj.GetValue("yMax");

        //    this.glyphs = new JsObj<FontGlyph>();
        //    var gobj = (JObject)jobj.GetValue("glyphs");
        //    var props = gobj.Properties();
        //    foreach (var prop in props)
        //    {
        //        var key = prop.Name;
        //        var vobj = (JObject)prop.Value;
        //        var glyph = new FontGlyph();
        //        glyph.ha = (double)vobj.GetValue("ha");
        //        glyph.o = (string)vobj.GetValue("o");
        //        glyph.x_min = (double)vobj.GetValue("x_min");
        //        glyph.x_max = (double)vobj.GetValue("x_max");
        //        this.glyphs.Add(key, glyph);
        //    }
        //    return this;
        //}
    }
    public class PathItem
    {
        public double offsetX;
        public ShapePath path;
    }
    public class Font
    {
        public string type;
        public FontData data;
        public Font(FontData data)
        {
            this.type = "Font";
            this.data = data;
        }

        public ListEx<Shape> generateShapes(string text, int size = 100)
        {

            var shapes = new ListEx<Shape>();
            var paths = createPaths(text, size, this.data);

            for (int p = 0, pl = paths.Length; p < pl; p++)
            {

                shapes.Push(paths[p].toShapes().ToArray());

            }

            return shapes;

        }


        public ListEx<ShapePath> createPaths(string text, int size, FontData data)
        {

            var chars = text.ToCharArray().ToListEx();
            var scale = size / data.resolution;
            var line_height = (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) * scale;

            var paths = new ListEx<ShapePath>();

            double offsetX = 0, offsetY = 0;

            for (var i = 0; i < chars.Length; i++)
            {

                var _char = chars[i];

                if (_char == '\n')
                {

                    offsetX = 0;
                    offsetY -= line_height;

                }
                else
                {

                    var ret = createPath(_char, scale, offsetX, offsetY, data);
                    offsetX += ret.offsetX;
                    paths.Push(ret.path);

                }

            }

            return paths;

        }

        public PathItem createPath(char _char, double scale, double offsetX, double offsetY, FontData data)
        {

            var glyph = data.glyphs[_char.ToString()] ?? data.glyphs["?"];

            if (glyph == null)
            {

                console.error("THREE.Font: character " + _char + " does not exists in font family " + data.familyName + ".");
                return null;
            }
            var path = new ShapePath();
            double x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
            if (glyph.o != null)
            {

                var outline = glyph._cachedOutline ?? (glyph._cachedOutline = glyph.o.split(" ").ToArray());

                for (int i = 0, l = outline.Length; i < l;)
                {

                    var action = outline[i++];

                    switch (action)
                    {

                        case "m": // moveTo

                            x = double.Parse(outline[i++]) * scale + offsetX;
                            y = double.Parse(outline[i++]) * scale + offsetY;

                            path.moveTo(x, y);

                            break;

                        case "l": // lineTo

                            x = double.Parse(outline[i++]) * scale + offsetX;
                            y = double.Parse(outline[i++]) * scale + offsetY;

                            path.lineTo(x, y);

                            break;

                        case "q": // quadraticCurveTo

                            cpx = double.Parse(outline[i++]) * scale + offsetX;
                            cpy = double.Parse(outline[i++]) * scale + offsetY;
                            cpx1 = double.Parse(outline[i++]) * scale + offsetX;
                            cpy1 = double.Parse(outline[i++]) * scale + offsetY;

                            path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);

                            break;

                        case "b": // bezierCurveTo

                            cpx = double.Parse(outline[i++]) * scale + offsetX;
                            cpy = double.Parse(outline[i++]) * scale + offsetY;
                            cpx1 = double.Parse(outline[i++]) * scale + offsetX;
                            cpy1 = double.Parse(outline[i++]) * scale + offsetY;
                            cpx2 = double.Parse(outline[i++]) * scale + offsetX;
                            cpy2 = double.Parse(outline[i++]) * scale + offsetY;

                            path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy);

                            break;

                    }

                }

            }
            return new PathItem { offsetX = glyph.ha * scale, path = path };
        }
    }
}
